C++ on MSVC講習/整数と小数
あらすじと概要
前回は整数、小数、文字列を実行時に保存したり、入力できるようになりました。 今回は、整数と小数の内部表現と、計算をやっていきましょう。
重要語
ビット
1/0を表現する情報の最小単位
バイト
概ね8bitである、情報の基本的な単位
1の補数表現
1の補数でマイナスの整数を表現する方法
2の補数表現
2の補数でマイナスの整数を表現する方法
符号ビット
値が正であるか負であるかを判定できるビット
オーバーフロー
扱える値の範囲を超えてしまうこと
ゼロ除算
値を0
で割ること
固定小数点数
予め小数点の位置を決定して整数で小数を表す方法
浮動小数点数
符号と仮数と指数で小数を表す方法
IEEE 754
一般的に用いられる浮動小数点数の規格
inf
infinityとも、無限大になる浮動小数点数演算の値
NaN
Not a Number、数値にならない浮動小数点数演算の値
正規化(正規表現)
指数表現の仮数を0以上10未満の表現にすること
丸め
値を表現できる値に近似すること、またその近似方法
サフィックス
接尾辞
プレフィックス
接頭辞
評価
式を実際に計算などして実行すること
ゼロ方向への丸め
小数部分を破棄し、ゼロに向かって丸めること
暗黙の型変換
型が違う場合で自動的に行われる型変換
キャスト
型変換、型を変更すること
整数の内部表現
組み込み型での計算を正確にするには、組み込み型の内部表現を知っているべきです。 ですからまず、一番基本的な整数の内部表現を学んでいきましょう。 前回保留していた、変数のサイズについても触れていきます。
ビットとバイト
コンピューターでは、電気的なOn/Offで1/0を表現し、2進数として扱っています。 最小となる、その1/0を表現する単位をビット、そして8bit ( ビット ) でバイトと扱われます。 なお、8bitで1Byte ( バイト ) とは決まっているわけでは無く、そうでない場合も極々稀にあります。
変数のサイズ
変数のサイズというのは、変数が使うビットの量のことになります。 ただし、ビットは物理的に存在している以上、無限に存在しているわけではありません。 また、変数のサイズは固定されていると何かと便利ですし、効率がよくなります。 以上のことから、変数にサイズを決める、あるいは決まってしまうのが普通です。
負の数を扱いたくなりました
負の数を扱いたくなりました。この時一番簡単に思いつくのは符号ビットを使うことです。 例えば、8bitの整数があった時、0 ( これ ) 000 0000
を符号ビットということにしてみましょう。 符号ビットが0
/1
の時、正/負とし、残り7bitは絶対値で表現してみましょう。0000 0001 ( 1 )
に対して、1000 0001 ( -1 )
のようになり、-127から127
が表現できます。
1の補数表現
符号ビットと絶対値で表す以外にも、正の数の表現をビット反転 ( 各ビットの0と1を逆にする ) する方法もあります。 例えば、上と同じく8bitの整数で、0000 0001 ( 1 )
をビット反転して1111 1110 ( -1 )
といった具合。 これもやっぱり-127から127
まで表現でき、0 ( これ ) 000 0000
は符号ビットの役割を果たします。 これは、数学の「補数」という考え方に由来するため、1の補数表現と言います。
ダメなのよ。
上の2つの表現、実はどっちもダメなところがあります。例えば、0
が2種類あるのです。 1つの数値に対して2つ以上表現があると、一意に表現できていないということになるのです。 でも、符号ビットが1
の時は0
を表現しない、とすると表現が1個捨てられて無駄です。
2の補数表現
どうすればいいのか。人類はやっぱり数学に由来して、2の補数表現にたどり着きました。 内容としては、負の数は、1の補数表現に+1
をした表現で表すことにしたのです。 つまり、8bitの-1
なら、0000 0001 ( 1 )
→1111 1110 ( 1の補数の-1 )
→1111 1111 ( 2の補数の-1 )
ということになりました。 これなら、例えば8bitであれば、127から-128
までを表せるのです。
何がいいのさ
実は、2の補数表現は減算を加算で処理できるという素晴らしい性質を持っています。36-12→24
なら、12
を-12
にして足すと0010 0100 ( 36 ) + 1111 0100 ( -12 ) → 0001 1000 ( 24 )
、あらあら。 桁あふれした分は見なかったことにして、2進法で計算すると減算が加算で処理できるのです。
C++の整数
C++17までは、C++はC言語と同様に2の補数以外の整数表現を許可していました。 しかし、C++20からは整数は2の補数表現に限られることになりました。 ただ、ほとんどは2の補数表現が用いられているので、気にするほどでもないかもしれません。 この講習では、整数は2の補数表現であることを前提にして進めていきます。
オーバーフローと符号付き整数
さて、負の値の扱い方まで分かると、整数が扱える値の範囲が分かると思います。 扱える値の範囲に制限があるということは、範囲を超えてしまったときはまずいのです。 そのことをオーバーフローといい、符号ありの変数では未定義動作となります。 なお、実行時に起こりうるエラーを検出するサニタイザーなどで検出することが出来ます。
オーバーフローと符号なし整数
符号なしの変数の場合には、その符号なし変数が表せる最大の値で剰余 ( じょうよ ) した値になります。 なお剰余、つまりあまり算は、割られる数を割る数で割った余りが求める値になる計算です。 例えば、8bitの符号なし整数 ( 扱える値の範囲は0~255 ) に対して、256
になる演算をしたら、1
になります。 以上のように、C++は単純な計算でも、変数でどの程度の値を扱うか把握するのが重要です。
ゼロ除算
オーバーフローの解説をしたので、同時にゼロ除算についても解説します。 名前の通り、値を0で割ることをゼロ除算と言い、C++では整数の場合未定義動作です。
MSVCにおける整数のサイズ
short
16bit
int(サイズに関する修飾子なし)
32bit
long
32bit
long long
64bit
MSVCにおける整数が扱える値の範囲
short (signed / unsigned)
-32,768 ~ 32,767 / 0 ~ 65,535
int (signed / unsigned)
-2,147,483,648 ~ 2,147,483,647 / 0 ~ 4,294,967,295
long (signed / unsigned)
-2,147,483,648 ~ 2,147,483,647 / 0 ~ 4,294,967,295
long long (signed / unsigned)
-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 / 0 ~ 18,446,744,073,709,551,615
小数の内部表現
さて、整数が分かりましたが、小数はどうなっているのでしょうか。
固定小数点数
これは、小数点の位置を予め決めておくことで、整数で小数を表す方法です。 整数で整数/小数部分を表現することから、基本的に誤差は発生しません。 その代わりに、扱える値の範囲が狭かったり、無駄が発生したりすることが多いのです。 そのため、一般的には使われず、特殊な分野で使用されることが多いです。
浮動小数点数
前回指数表記を解説しましたが、その発想に近いのが浮動小数点数です。 すなわち、符号と仮数と指数で小数を表す表現をするのが浮動小数点数です。 現在の多くのコンピューターでは、専用のプロセッサ ( FPU, Floating Point Unit ) があり、主流となっています。
IEEE ( あいとりぷるいー ) 754
浮動小数点数の実装や扱いとして一般的に用いられるのが、IEEE 754です。 IEEE 754の中でも、C++で使われているのはbinary32/binary64 ( ばいなりー32/ばいなりー64, 俗に単精度/倍精度 ) の2つです。 binary32は合計32bit、binary64は合計64bitを用いる表現方法です。 以下では、特に言及がない場合はIEEE 754に基づいて解説します。
特殊な値
浮動小数点数には、整数にはない特殊な値、inf ( infinity, いんふぃにてぃー ) とNaN ( なん, Not a Number ) があります。 infは無限大のことで、非0のゼロ除算などの演算で発生し、正負が存在します。 NaNは0のゼロ除算や、infと0の乗算、inf同士の除算、異符号infの加算などで発生します。 なおinfは0の対数を取った時、NaNは結果が虚数になる演算などでも設定される事があります。
指数表記のおさらい
指数表記は、1.25e5などのように表記する方法で、符号/仮数/基数/指数に分けられます。 例えば、-1.25e5
なら符号/仮数/基数/指数
は-/1.25/e/5
となります。 符号が+の場合は+が省略されますが符号は+です。また、e
は10
として扱います。 基数がeではない場合は、紙に書く時と同じようにします。例えば-1.25*2**5
のように。
正規化
指数表記において、仮数を0以上10未満にした表現に変換することを正規化と言います。 例えば、12.345e2
という値であれば、1.2345e3
という形に正規化することが出来ます。
基数と丸め
binary32/64では、基数は2
と定められます。つまり、2進数の小数ということになります。 そのため、10進数の小数を2進数で表すときや、除算で循環小数になる事があるのです。 この場合に、何らかの値に近似するする必要があり、その近似方法を丸めと言います。 主に、最も近くの表現できる値へ、丁度中間の値なら偶数へ丸める方法が用いられます。
精度
binary32では、2進数で23~24bit程度、10進数で6~7桁程度の精度を持っています。 binary64では、2進数で52~53bit程度、10進数で15桁程度の精度を持っています。 ここで示す精度より大きい桁については、不確かな値が含まれることになります。
C++における小数の扱い
C++の標準規格では、固定小数点数か浮動小数点数かすら決められていません。 ただし、主要な処理系では、floatをbinary32、doubleをbinary64で実装しています。 long doubleについては、MSVCはdoubleと同様binary64で実装していますが、 他の処理系ではより高精度かもしれません。ただ、通常doubleを使用すればよいでしょう。
MSVCにおける小数の扱い
float
binary32
double
binary64
long double
binary64
MSVCにおける小数が扱える値の範囲
float
3.4E±38
double
1.7E±308
long double
1.7E±308
整数と小数の演算
それでは、やっと今回初めてのサンプルコードです。
整数と小数の演算
COPY
#include < iostream>
int main()
{
std:: cout << 0b1010 + 020 << "\n"
<< 0xFF - 1.5 << "\n"
<< 2.5 * 8 << "\n"
<< 1ul / 3ull << "\n"
<< ( int ) 1.5 << "\n"
<< ( double ) 1 / 3 << "\n"
<< 1 / static_cast < double >( 3 ) << "\n"
<< 15 % 8 ;
}
実行結果例
COPY
26 253.5 20 0 1 0.333333 0.333333 7
解説
解説します
リテラルの型
既に分かっているかもしれませんが、リテラルもやはり型を持っています。 整数/小数リテラルそれぞれ標準で、int/double型になります。 整数/小数リテラルは、それぞれ、以下のサフィックス ( suffix, 接尾辞の総称 ) を付けると型を変えられます。 整数リテラルのサフィックスは、符号とサイズについては組み合わせることが出来ます。 ただしリテラルの値が、指定されている型で表せない場合、より大きい型になります。
整数リテラルのサフィックス
signed
なし
unsigned
u または U
int
なし
long
l または L
long long
ll または LL
小数リテラルのサフィックス
float
f または F
double
なし
long double
l または L
整数リテラルの進法選択
更に、整数リテラルは2/8/10/16進法の何れかを使用できます。 これらは、以下のプレフィックス ( prefix, 接頭辞の総称 ) を使用して選択することが出来ます。 16進法では、a/A
~f/F
まで、順番に10
~15
を表します。 特に、始めを0
にすると、8進法となってしまうことに注意しましょう。
整数リテラルのプレフィックス
2進法
0b
8進法
0
10進法
なし
16進法
0x
各進法で使用できる数字と文字
2進法
0/1
8進法
0/1/2/3/4/5/6/7
10進法
0/1/2/3/4/5/6/7/8/9
16進法
0/1/2/3/4/5/6/7/8/9/a/A/b/B/c/C/d/D/e/E/f/F
式と評価
式は演算子とオペランド(被演算子)の並びですが、結果的には値になります。 式を計算することを評価すると言うので、式は評価されると値になると言えます。
演算子について
演算子は式を構成する要素ですが、演算子は主オペランドを1/2/3つ持つものがあります。 1つのオペランドを取るものは、オペランドの前/後に置くかで、更に前置/後置と区別します。 なお、オペランドは値に評価されるものであればよいので、式も取ることが出来ます。
今回用いた演算子
+
加算
-
減算
*
乗算
/
除算
%
剰余 ( じょうよ ) 、モジュロ、余り算とも
問題の箇所と0方向への丸め
1ul ( unsigned longの1 ) / 3ull ( unsigned long longの3 )
が0に評価されるのは驚いたでしょうか。 これは、C++では整数同士の計算結果は、やはり整数になるからなのです。 整数同士の計算が数学的に小数になる場合、0方向に丸められます。 つまり、小数部分を見なかったことにして、整数部分が計算結果になるということです。
暗黙の型変換
一般に、式のオペランドの型が違う場合、暗黙の型変換というものが行われる事があります。 組み込み型では、2つのオペランドのうち、より表せる値の範囲が広い方へ変換されます。 整数/小数同士では、単純により大きい方へと、整数と小数では、小数へ変換されます。 ただし、整数のsignedとunsignedが一致しない場合、unsignedへと変換されます。
整数と小数についての暗黙の型変換
整数同士
short < int < long < long long
, signed < unsigned
小数同士
float < double < long double
整数と小数
整数 < 小数
キャスト(型変換、明示的な型変換)
暗黙の型変換以外にも明示的に型を変換でき、それをキャストと言います。 キャストは、まずCスタイルのキャストと、C++で追加されたキャストがあります。 C++で追加されたキャストは、Cスタイルのキャストをより役割ごとに分割したものです。
Cスタイルのキャスト
Cスタイルのキャストの中で一番使われる構文は以下のようになります。 Cスタイルのキャストは、C++で追加されたキャスト演算子を組み合わせてキャストされます。 そのため、記述するのは楽ですが、余りにも強力な為、あまり推奨されません。
Cスタイルのキャストの構文
COPY
(
型名 )
値
static_cast
static_castは演算子で、暗黙の型変換で行われるようなキャストを明示的に行えます。 構文は以下のようで、<>
の中に型名、()
の中にキャストしたい値を入れます。
static_cast
COPY
static_cast
<
型名 >
(
値 )
問題の箇所2
(int)1.5
は1
に評価されます。小数から整数へのキャストでも、0方向へ丸められるのです。 そして、(double)1 / 3
と1 / static_cast<double>(3)
は、片方をdoubleにしています。 その結果、もう片方が暗黙の型変換でdoubleになり、評価はdoubleとなるのです。
剰余
前半でも説明しましたが、割られる数を割る数で割った時の余りが、剰余で得られる値です。 剰余演算子は挙げた5つの演算子の中で唯一、オペランドに取れるのが整数だけとなります。 まあ、剰余はnの倍数かの判定くらいでしか使用しないので[要検証]いいでしょう。 余剰は除算同様に、ゼロの余剰を取ろうとすると未定義動作になります。
参照、出典
参照や出典です
参照
ビット - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88
バイト (情報) - Wikipedia
https://ja.wikipedia.org/wiki/ %E3%83%90%E3%82%A4%E3%83%88_(%E6%83%85%E5%A0%B1)
補数 - Wikipedia
https://ja.wikipedia.org/wiki/%E8%A3%9C%E6%95%B0
符号付数値表現 - Wikipedia
https://ja.wikipedia.org/wiki/ %E7%AC%A6%E5%8F%B7%E4%BB%98%E6%95%B0%E5%80%A4%E8%A1%A8%E7%8F%BE
2の補数 - Wikipedia
https://ja.wikipedia.org/wiki/2%E3%81%AE%E8%A3%9C%E6%95%B0
符号付き整数型が2の補数表現であることを規定 - cpprefjp C++日本語リファレンス
https://cpprefjp.github.io/lang/cpp20/signed_integers_are_twos_complement.html
算術演算子 - cppreference.com
https://ja.cppreference.com/w/cpp/language/operator_arithmetic
固定小数点数 - Wikipedia
https://ja.wikipedia.org/wiki/ %E5%9B%BA%E5%AE%9A%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
データ型の範囲 | Microsoft Docs
https://docs.microsoft.com/ja-jp/cpp/cpp/data-type-ranges?view=msvc-160
浮動小数点数 - Wikipedia
https://ja.wikipedia.org/wiki/ %E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0
IEEE 754 - Wikipedia
https://ja.wikipedia.org/wiki/IEEE_754
小数の表現
http://www.cc.kyoto-su.ac.jp/~kbys/kiso/number/fraction.html
端数処理 - Wikipedia
https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%95%B0%E5%87%A6%E7%90%86
整数リテラル - cppreference.com
https://ja.cppreference.com/w/cpp/language/integer_literal
浮動小数点リテラル - cppreference.com
https://ja.cppreference.com/w/cpp/language/floating_literal
評価順序 - cppreference.com
https://ja.cppreference.com/w/cpp/language/eval_order
C++の演算子の優先順位 - cppreference.com
https://ja.cppreference.com/w/cpp/language/operator_precedence
暗黙の変換 - cppreference.com
https://ja.cppreference.com/w/cpp/language/implicit_conversion
明示的な型変換 - cppreference.com
https://ja.cppreference.com/w/cpp/language/explicit_cast
static_cast 変換 - cppreference.com
https://ja.cppreference.com/w/cpp/language/static_cast